#!/usr/bin/env python
# -*- coding:utf8 -*-
# uncompyle6 version 2.15.1
# Python bytecode 2.7 (62211)
# Decompiled from: Python 2.7.10 (default, Jul  1 2017, 13:36:56) 
# [GCC 4.4.6 20110731 (Red Hat 4.4.6-4)]
# Embedded file name: ./login/bkaccount/accounts.py
# Compiled at: 2017-11-16 15:44:28
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.

账号体系相关的基类Account
"""
import datetime, time, unicodedata
from django.conf import settings
from django.contrib.auth import login as auth_login, logout as auth_logout
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.views import redirect_to_login
from django.contrib.sites.shortcuts import get_current_site
from django.http import HttpResponseRedirect, HttpResponse
from django.shortcuts import resolve_url
from django.template.response import TemplateResponse
from django.utils.six.moves.urllib.parse import urlparse
from common.log import logger
from bkaccount.encryption import encrypt, decrypt, salt
from bkaccount.models import Loignlog, BkToken

class AccountSingleton(object):
    """
    单例基类
    """
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not isinstance(cls._instance, cls):
            cls._instance = object.__new__(cls, *args, **kwargs)
        return cls._instance


class Account(AccountSingleton):
    """
    账号体系相关的基类Account
    
    提供通用的账号功能
    """
    BK_COOKIE_NAME = settings.BK_COOKIE_NAME
    BK_COOKIE_AGE = settings.BK_COOKIE_AGE
    REDIRECT_FIELD_NAME = 'c_url'
    BK_LOGIN_URl = str(settings.LOGIN_URL)
    BK_TOKEN_OFFSET_ERROR_TIME = settings.BK_TOKEN_OFFSET_ERROR_TIME

    def is_safe_url(self, url, host=None):
        """
        判断url是否与当前host的根域一致
        
        以下情况返回False：
            1)根域不一致
            2)url的scheme不为：https(s)
            3)url为空
        """
        if url is not None:
            url = url.strip()
        if not url:
            return False
        url = url.replace('\\', '/')
        if url.startswith('///'):
            return False
        url_info = urlparse(url)
        if not url_info.netloc and url_info.scheme:
            return False
        if unicodedata.category(url[0])[0] == 'C':
            return False
        url_domain = url_info.netloc.split(':')[0].split('.')[-2] if url_info.netloc else ''
        host_domain = host.split(':')[0].split('.')[-2] if host else ''
        return (not url_info.netloc or url_domain == host_domain) and (not url_info.scheme or url_info.scheme in ('http',
                                                                                                                  'https'))

    def get_bk_token(self, username):
        """
        生成用户的登录态
        """
        bk_token = ''
        expire_time = int(time.time())
        retry_count = 0
        while not bk_token and retry_count < 5:
            now_time = int(time.time())
            expire_time = now_time + self.BK_COOKIE_AGE
            plain_token = '%s|%s|%s' % (expire_time, username, salt())
            bk_token = encrypt(plain_token)
            try:
                BkToken.objects.create(token=bk_token)
            except:
                logger.exception(u'登录票据保存失败')
                bk_token = '' if retry_count < 4 else bk_token

            retry_count += 1

        return (bk_token, datetime.datetime.fromtimestamp(expire_time))

    def _is_bk_token_valid(self, bk_token):
        """
        验证用户登录态
        """
        if not bk_token:
            error_msg = u'缺少参数bk_token'
            return (
             False, error_msg)
        try:
            plain_bk_token = decrypt(bk_token)
        except:
            plain_bk_token = ''
            logger.exception(u'参数[%s]解析失败' % bk_token)

        error_msg = u'参数bk_token非法'
        if not plain_bk_token:
            return (False, error_msg)
        token_info = plain_bk_token.split('|')
        if not token_info or len(token_info) < 3:
            return (False, error_msg)
        try:
            is_logout = BkToken.objects.get(token=bk_token).is_logout
        except:
            error_msg = u'不存在bk_token[%d]的记录' % bk_token
            return (
             False, error_msg)

        expire_time = int(token_info[0])
        now_time = int(time.time())
        if is_logout:
            error_msg = u'登录态已注销'
            return (
             False, error_msg)
        if now_time > expire_time + self.BK_TOKEN_OFFSET_ERROR_TIME:
            error_msg = u'登录态已过期'
            return (
             False, error_msg)
        if expire_time - now_time > self.BK_COOKIE_AGE + self.BK_TOKEN_OFFSET_ERROR_TIME:
            error_msg = u'登录态有效期不合法'
            return (
             False, error_msg)
        username = token_info[1]
        return (
         True, username)

    def is_bk_token_valid(self, request):
        bk_token = request.COOKIES.get(self.BK_COOKIE_NAME, None)
        return self._is_bk_token_valid(bk_token)

    def record_login_log(self, request, user, app_id):
        """
        记录用户登录日志
        """
        host = request.get_host()
        login_browser = request.META.get('HTTP_USER_AGENT', 'unknown')
        login_ip = request.META.get('HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR')
        Loignlog.objects.record_login(user, login_browser, login_ip, host, app_id)

    def redirect_login(self, request):
        """
        重定向到登录页面.
        
        登录态验证不通过时调用
        """
        if request.is_ajax():
            return HttpResponse(status=401)
        path = request.build_absolute_uri()
        resolved_login_url = resolve_url(self.BK_LOGIN_URl)
        login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
        current_scheme, current_netloc = urlparse(path)[:2]
        if (not login_scheme or login_scheme == current_scheme) and (not login_netloc or login_netloc == current_netloc):
            path = settings.SITE_URL[:-1] + request.get_full_path()
        return redirect_to_login(path, resolved_login_url, self.REDIRECT_FIELD_NAME)

    def login(self, request, template_name='account/login.html', authentication_form=AuthenticationForm, current_app=None, extra_context=None):
        """
        登录页面和登录动作
        """
        redirect_field_name = self.REDIRECT_FIELD_NAME
        redirect_to = request.POST.get(redirect_field_name, request.GET.get(redirect_field_name, ''))
        app_id = request.POST.get('app_id', request.GET.get('app_id', ''))
        if request.method == 'POST':
            form = authentication_form(request, data=request.POST)
            if form.is_valid():
                if not self.is_safe_url(url=redirect_to, host=request.get_host()):
                    redirect_to = resolve_url('%saccounts/' % settings.SITE_URL)
                auth_login(request, form.get_user())
                username = form.cleaned_data.get('username', '')
                self.record_login_log(request, form.get_user(), app_id)
                bk_token, expire_time = self.get_bk_token(username)
                response = HttpResponseRedirect(redirect_to)
                response.set_cookie(self.BK_COOKIE_NAME, bk_token, expires=expire_time, domain=settings.BK_COOKIE_DOMAIN)
                return response
        else:
            form = authentication_form(request)
        current_site = get_current_site(request)
        context = {'form': form,
           redirect_field_name: redirect_to,
           'site': current_site,
           'site_name': current_site.name,
           'app_id': app_id
           }
        if extra_context is not None:
            context.update(extra_context)
        if current_app is not None:
            request.current_app = current_app
        bk_token = request.COOKIES.get(self.BK_COOKIE_NAME, None)
        if bk_token:
            BkToken.objects.filter(token=bk_token).update(is_logout=True)
        response = TemplateResponse(request, template_name, context)
        response.delete_cookie(self.BK_COOKIE_NAME, domain=settings.BK_COOKIE_DOMAIN)
        return response

    def logout(self, request, next_page=None):
        """
        登出并重定向到登录页面
        """
        redirect_field_name = self.REDIRECT_FIELD_NAME
        auth_logout(request)
        if redirect_field_name in request.POST or redirect_field_name in request.GET:
            next_page = request.POST.get(redirect_field_name, request.GET.get(redirect_field_name))
            if not self.is_safe_url(url=next_page, host=request.get_host()):
                next_page = request.path
        if next_page:
            response = HttpResponseRedirect(next_page)
        else:
            response = HttpResponseRedirect(self.BK_LOGIN_URl)
        bk_token = request.COOKIES.get(self.BK_COOKIE_NAME, None)
        if bk_token:
            BkToken.objects.filter(token=bk_token).update(is_logout=True)
        response.delete_cookie(self.BK_COOKIE_NAME, domain=settings.BK_COOKIE_DOMAIN)
        return response
